<template>
  <div class="tree-node">
    <div
      class="tree-node-content"
      :class="{
        'is-selected': selectionStatus === 'full',
        'is-partial': selectionStatus === 'partial',
      }"
      :style="indentStyle"
      @click="$emit('toggle-select', node)"
    >
      <div
        class="expand-icon"
        @click.stop="$emit('toggle-expand', getNodeValue(node))"
      >
        <div
          v-if="hasChildren"
          class="arrow-right"
          :class="{ 'is-expanded': isExpanded }"
        ></div>
      </div>

      <div class="node-checkbox">
        <div
          class="checkbox-wrapper"
          :class="{
            'is-checked': selectionStatus === 'full',
            'is-partial': selectionStatus === 'partial',
          }"
        >
          <div v-if="selectionStatus === 'full'" class="check-mark"></div>
          <div v-if="selectionStatus === 'partial'" class="partial-mark"></div>
        </div>
      </div>

      <span class="node-label">{{ getNodeLabel(node) }}</span>
    </div>

    <div v-if="hasChildren && isExpanded" class="tree-node-children">
      <TreeNode
        v-for="child in getNodeChildren(node)"
        :key="getNodeValue(child)"
        :node="child"
        :level="level + 1"
        :selected-values="selectedValues"
        :expanded-keys="expandedKeys"
        :field-names="fieldNames"
        @toggle-expand="$emit('toggle-expand', $event)"
        @toggle-select="$emit('toggle-select', $event)"
      />
    </div>
  </div>
</template>

<script>
export default {
  name: "TreeNode",
  props: {
    node: {
      type: Object,
      required: true,
    },
    level: {
      type: Number,
      default: 0,
    },
    selectedValues: {
      type: Array,
      default: () => [],
    },
    expandedKeys: {
      type: Set,
      default: () => new Set(),
    },
    fieldNames: {
      type: Object,
      default: () => ({
        label: "label",
        value: "value",
        children: "children",
      }),
    },
  },
  emits: ["toggle-expand", "toggle-select"],
  computed: {
    isSelected () {
      return this.selectedValues.includes(this.getNodeValue(this.node));
    },
    isExpanded () {
      return this.expandedKeys.has(this.getNodeValue(this.node));
    },
    hasChildren () {
      const nodeChildren = this.getNodeChildren(this.node);
      return nodeChildren && nodeChildren.length > 0;
    },
    indentStyle () {
      return {
        paddingLeft: `${15 + this.level * 20}px`,
      };
    },
    // 获取节点的选择状态：full(全选), partial(半选), none(未选)
    selectionStatus () {
      if (!this.hasChildren) {
        return this.isSelected ? "full" : "none";
      }

      const allDescendants = this.getAllDescendants(this.node);
      const selectedDescendants = allDescendants.filter((node) =>
        this.selectedValues.includes(this.getNodeValue(node)),
      );

      if (selectedDescendants.length === 0) {
        return "none";
      } else if (selectedDescendants.length === allDescendants.length) {
        return "full";
      } else {
        return "partial";
      }
    },
  },
  methods: {
    // 获取某个节点的所有后代节点
    getAllDescendants (node) {
      const descendants = [];
      const traverse = (nodeList) => {
        nodeList.forEach((childNode) => {
          descendants.push(childNode);
          const childNodeChildren = this.getNodeChildren(childNode);
          if (childNodeChildren && childNodeChildren.length > 0) {
            traverse(childNodeChildren);
          }
        });
      };
      const nodeChildren = this.getNodeChildren(node);
      if (nodeChildren && nodeChildren.length > 0) {
        traverse(nodeChildren);
      }
      return descendants;
    },

    // 获取节点的指定字段值
    getNodeValue (node) {
      return node[this.fieldNames.value];
    },

    getNodeLabel (node) {
      return node[this.fieldNames.label];
    },

    getNodeChildren (node) {
      return node[this.fieldNames.children];
    },
  },
};
</script>

<style lang="less" scoped>
@primary-color: #4b58dc;
@transition-time: 0.3s;

.tree-node {
  .tree-node-content {
    display: flex;
    align-items: center;
    padding: 8px 15px 8px 0;
    cursor: pointer;
    transition: all @transition-time ease;
    position: relative;
    overflow: hidden;

    &:after {
      content: "";
      position: absolute;
      top: 0;
      left: -75%;
      width: 50%;
      height: 100%;
      background: linear-gradient(
        to right,
        rgba(255, 255, 255, 0),
        rgba(255, 255, 255, 0.4),
        rgba(255, 255, 255, 0)
      );
      transform: skewX(-15deg);
      transition: all 0.5s ease;
      opacity: 0;
    }

    &:hover {
      background-color: rgba(75, 88, 220, 0.05);

      &:after {
        left: 120%;
        opacity: 1;
      }

      .node-label {
        color: @primary-color;
      }
    }

    &.is-selected {
      background: linear-gradient(
        to right,
        rgba(75, 88, 220, 0.08),
        rgba(75, 88, 220, 0.02)
      );
      border-left: 3px solid @primary-color;

      .node-label {
        color: @primary-color;
        font-weight: 500;
      }
    }

    &.is-partial {
      background: linear-gradient(
        to right,
        rgba(75, 88, 220, 0.04),
        rgba(75, 88, 220, 0.01)
      );
      border-left: 2px solid rgba(75, 88, 220, 0.6);

      .node-label {
        color: rgba(75, 88, 220, 0.8);
        font-weight: 400;
      }
    }

    .expand-icon {
      width: 20px;
      height: 20px;
      display: flex;
      align-items: center;
      justify-content: center;
      margin-right: 5px;

      .arrow-right {
        width: 0;
        height: 0;
        border-top: 4px solid transparent;
        border-bottom: 4px solid transparent;
        border-left: 6px solid #c0c4cc;
        transition: transform @transition-time ease;

        &.is-expanded {
          transform: rotate(90deg);
        }
      }
    }

    .node-checkbox {
      margin-right: 8px;

      .checkbox-wrapper {
        width: 16px;
        height: 16px;
        border: 1px solid #dcdfe6;
        border-radius: 2px;
        display: flex;
        align-items: center;
        justify-content: center;
        transition: all @transition-time ease;
        background: #fff;

        &.is-checked {
          background: linear-gradient(
            135deg,
            @primary-color,
            lighten(@primary-color, 20%)
          );
          border-color: @primary-color;

          .check-mark {
            width: 8px;
            height: 4px;
            border-left: 2px solid #fff;
            border-bottom: 2px solid #fff;
            transform: rotate(-45deg);
            animation: check-scale 0.3s forwards;
          }
        }

        &.is-partial {
          background: rgba(75, 88, 220, 0.1);
          border-color: @primary-color;

          .partial-mark {
            width: 8px;
            height: 2px;
            background: @primary-color;
            border-radius: 1px;
            animation: check-scale 0.3s forwards;
          }
        }

        &:hover {
          border-color: @primary-color;
        }
      }
    }

    .node-label {
      font-size: 14px;
      color: #606266;
      transition: color @transition-time ease;
      flex: 1;
    }
  }

  .tree-node-children {
    border-left: 1px dashed #e4e7ed;
    margin-left: 25px;
  }
}

@keyframes check-scale {
  0% {
    transform: scale(0) rotate(-45deg);
  }
  50% {
    transform: scale(1.2) rotate(-45deg);
  }
  100% {
    transform: scale(1) rotate(-45deg);
  }
}
</style>
